<?php
/*
 * Copyright (c) 2008 Sergey Bondari, bondari@bondari.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Mosaic\System;


/**
 * Object broker subsystem. 
 *
 * Object broker subsystem is responsible for remote object invocations. 
 * It stores instances, performs methods calls and contains universal skeleton
 * code and server side representation of GRT garbage collection algorithm.
 * GRT vector is stored with the object instance in pool.
 *
 *
 * @package System
 * @subpackage Node
 */
class ObjectBroker {

	/**
	 * Object instances pool
	 * @var array Array of instances and GRT vectors
	 */
	private $pool;

	/**
	 * Array of all class names handled by broker
	 * @var array Array of string
	 */
	private $classes;


	
	/**
	 * Object broker initialization routine
	 *
 	 * Function reads local node manifest data and loads all remote objects information
	 * broker has to service
	 *
	 * @return void
	 */
	public function start() {
		global $sys;

		$this->pool = array();
		foreach ($sys->componentBroker->localPackages->findResourceByType('mosaic:resource:class') as $resource) {
			if ($resource->isRemoteObjectClass()) {
				try {
					trigger_error('Loading skeleton of '.$resource->getClassName());
					$resource->prepare();
					trigger_error('Registering class with nameserver');

					$req = new RemoteClassManifest($resource->getClassName(), $sys->name, $resource->getStubCode());
					trigger_error("Sending class manifest ...");
					$response = $req->sendReceive($sys->nsSocket);
					/* Would raise an exception in case of any error on NS */
					$response->getData();

					$this->classes[] = $resource->getClassName();
					trigger_error(sprintf('[%s] registered with the object broker.', $resource->getClassName()));
				} catch (Exception $e) {
					trigger_error($e->getMessage(), E_USER_ERROR);
					trigger_error(sprintf('[%s] class registration with the object broker has failed', $resource->getClassName()), E_USER_ERROR);
				}
			}
		}
		
	}


	/**
	 * Object instantiation request processing
	 *
 	 * New URI is generated, object instantiated, placed into the pool and URI is returned to invocator.
	 *
	 * @param RemoteObjectInstantiateRequest
	 * @return string Object intance URI
	 */
	public function instantiate(RemoteObjectInstantiateRequest $req) {
		global $sys;
		static $x;

		$className = $req->getClassName();
		$args = $req->getArgs();

		if (!class_exists($className) || !in_array($className, $this->classes)) {
			throw new RuntimeException('Remote class '.$className.' is unknown to object broker.');
		}

		$x++;
		$uri = 'mcgi://'.$sys->name.'/'.$className.'/'.$x;
		trigger_error('Instantiating '.$uri);
		$this->pool[$uri] = array(new $className($args), array(0=>1));
		return $uri;
	}


	/**
	 * Method invocation request
	 *
	 * Method uses call_user_func_array() PHP function to perform calls.
 	 * For garbage collection GRT algorithm messages object broker reserves two 
	 * method names __bind() and __unbind()
	 *
	 * @param RemoteObjectCallRequest
	 * @return string Object intance URI
	 */
	public function call(RemoteObjectCallRequest $req) {
		global $sys;

		if (!isset($this->pool[$req->getUri()])) {
			trigger_error('Do not have instance '.$req->getUri().' in my pool', E_USER_ERROR);
			throw new RuntimeException('Do not have instance '.$req->getUri().' in my pool');
		}


		switch ($req->getMethod()) {
			case '__bind': // Explicite binding - manually created reference to the object (exception to the classic GRT algorithm
				$args = $req->getArgs();
				$grtGen = $args[0];
				$grtCpy = $args[1];
				trigger_error('Explicite binding of '.$req->getUri().', G(S)='.$grtGen.', C(S)='.$grtCpy);
				if (!isset($this->pool[$req->getUri()])) throw new RuntimeException('Do not have '.$req->getUri().' in my pool');

				@$this->pool[$req->getUri()][1][$grtGen]++;
				@$this->pool[$req->getUri()][1][$grtGen+1] += $grtCpy;
			break;

			case '__unbind': // GRT destroy message
				$args = $req->getArgs();
				$grtGen = $args[0]; // GRT generation number (first parameter)
				$grtCpy = $args[1]; // GRT number of copies (second parameter)


				trigger_error('Unbinding '.$req->getUri().', G(S)='.$grtGen.', C(S)='.$grtCpy);

				/* 
				 * Get GRT vector for the object
				 */
				$grt = $this->pool[$req->getUri()][1];


				/*
				 * Adjust GRT vector
				 */
				if (!isset($grt[$grtGen])) $grt[$grtGen] = 0;
				if (!isset($grt[$grtGen+1])) $grt[$grtGen+1] = 0;
				$grt[$grtGen]--; 


				@$grt[$grtGen+1] += $grtCpy;

				$any = false;


				/*
				 * Check if GRT is zero vector	
				 * and release instance
				 */
				foreach ($grt as $_k => $_v)  if ($_v) $any=true; else unset($grt[$_k]);

				if (!$any) {
					trigger_error('GRT is 0-vector. Destructing object');
					unset($this->pool[$req->getUri()]);
					gc_collect_cycles();
				} else {
					$this->pool[$req->getUri()][1] = $grt;
				}
			break;
			
			default:

				/*
				 * Perform a call
				 */
				if (!is_callable(array($this->pool[$req->getUri()][0], $req->getMethod()))) {
					throw new RuntimeException('Method '.$req->getMethod().' is not callable on '.$req->getUri());
				}

				trigger_error('Calling '.$req->getUri().'::'.$req->getMethod().'()');
				return call_user_func_array(array($this->pool[$req->getUri()][0], $req->getMethod()), $req->getArgs());

		}

	}


	/**
	 * Processing of get stub code request
	 *
	 * @param RemoteObjectStubRequest
	 * @return string PHP code of stub
	 */
	public function getStubCode(RemoteObjectStubRequest $req) {
		global $sys;
		try {
			$class = $sys->componentBroker->localPackages->findResourceByKey(ResourceInfo::createKey('mosaic:resource:class', $req->getClassName()));
			return $class->getStubCode();
		} catch (Exception $e) {
			throw new RuntimeException('Unable to find remote class stub for '.$req->getClassName());
		}
	}
}
